/* 
 * Copyright 2010 Ziya Guven Koylu, Ozge Koylu
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.koylu.caffein.model.clazz;

import java.util.Collection;

import com.koylu.caffein.converter.Converter;
import com.koylu.caffein.converter.ConverterManager;
import com.koylu.caffein.exception.ClazzConfigException;
import com.koylu.caffein.model.caffein.CaffeinConfig;
import com.koylu.caffein.model.clazz.id.CompositeId;
import com.koylu.caffein.model.clazz.id.Id;

public class Key implements Column {
	
	private String column;
	private String columnAlias;
	private String refColumn;
	private String converter;
	private Converter converterObject;
	private Node parent;
	private ColumnField realNode;
	private String path;
	private String pathForId;

	public String getColumn() {
		return column;
	}

	public void setColumn(String column) {
		this.column = column;
	}
	
	public String getColumnAlias() {
		return columnAlias;
	}
	
	public void setColumnAlias(String columnAlias) {
		this.columnAlias = columnAlias;
	}

	public String getConverter() {
		return converter;
	}

	public void setConverter(String converter) {
		this.converter = converter;
	}
	
	public void setConverterObject(Converter converterObject) {
		this.converterObject = converterObject;
	}

	public Node getParent() {
		return parent;
	}

	public void setParent(Node parent) {
		this.parent = parent;
	}

	public String getRefColumn(CaffeinConfig caffeinConfig) throws Exception{
		if( refColumn == null || "".equals(refColumn) ){
			Id id = null;
			Clazz clazz = null;
			if(this instanceof ForeignKey){
				clazz = ((Relation)getParent()).getForeignClazz(caffeinConfig);
			}else{
				Node node = getParent();
				while(node.getParent() != null) {//!(node instanceof Clazz)	
					node = node.getParent();
				}
				clazz = (Clazz)node;
			}
			id = clazz.getId();
			if( !(id instanceof CompositeId) ){
				refColumn = ((Column)id).getColumn();
			}else{
				throw new ClazzConfigException("ref-coumn value can not be null foreign clazz id is composite", this);
			}
		}
		return refColumn;
	}
	
	public String getRefColumn(){
		return refColumn;
	}

	public void setRefColumn(String refColumn) {
		this.refColumn = refColumn;
	}
	
	public Column getRefNode(CaffeinConfig caffeinConfig) throws Exception{
		return getRealNode(caffeinConfig);
	}
	
	public void setColumnType(String columnTypeName) throws Exception{
		if( !"".equals(columnTypeName) && columnTypeName != null ){
			try {
				columnType = ConverterManager.getJdbcTypeValue(columnTypeName);
				if(columnType == null){
					throw new ClazzConfigException("column-type not found:"+columnTypeName, this);
				}
			} catch (Exception e) {
				throw e;
			}			
		}
	}
	
	public void setColumnNullable(boolean columnNullable) throws Exception{
		this.columnNullable = columnNullable;
	}

	//Key --> ColumnField 
	public ColumnField getRealNode(CaffeinConfig caffeinConfig) throws Exception{
		if (realNode == null) {
			Node clazz = getParent();
			while(clazz.getParent() != null) {//!(clazz instanceof Clazz)
				clazz = clazz.getParent();
			}
			realNode = (ColumnField) ((Clazz) clazz).getColumn(getRefColumn(caffeinConfig));
			if( realNode == null ){
				throw new ClazzConfigException("column:"+refColumn+" not found in the clazz", this);
			}
		}
		return realNode;
	}

	public String getPath(CaffeinConfig caffeinConfig) throws Exception{
		if (path == null) {
			Node clazz = getParent();
			while(clazz.getParent() != null) {//!(clazz instanceof Clazz)
				path = ((Field) clazz).getName() + (path == null ? "" : "." + path);
				clazz = clazz.getParent();
			}
			Column column = ((Clazz) clazz).getColumn(getRefColumn(caffeinConfig));
			if(column == null){
				throw new ClazzConfigException("column:"+refColumn+" not found in the clazz", this);
			}
			path = path + "." + column.getPath(caffeinConfig);
		}
		return path;
	}

	public String getPathForId(CaffeinConfig caffeinConfig) throws Exception{
		if (pathForId == null) {
			pathForId = getPath(caffeinConfig);
			if (pathForId.contains(".")) {
				pathForId = pathForId.substring(pathForId.indexOf(".") + 1, pathForId.length());
			} else {
				pathForId = "";
			}
		}
		return pathForId;
	}

	public Converter getConverter(CaffeinConfig caffeinConfig) throws Exception {
		if (converterObject == null) {
			int columnType = getColumnType(caffeinConfig);
			Class<?> fieldType = getRealNode(caffeinConfig).getRealClass(caffeinConfig);
			if(fieldType.isPrimitive()){
				Class<?> temp = ConverterManager.getPrimitiveMap().get(fieldType);
				if(temp != null){
					fieldType = temp;
				}
			}
			if (converter != null && !"".equals(converter)) {
				try {
					converterObject = (Converter) Class.forName(converter).newInstance();
					converterObject.setJdbcType(columnType);
					converterObject.setFieldType(fieldType);
				} catch (Exception e) {
					throw new ClazzConfigException("invalid converter class:"+converter, e, this);
				}
			} else {
				converterObject = ConverterManager.getConverterObject(columnType, fieldType);
				if(converterObject == null){
					throw new ClazzConfigException("converter not found for columnType:"+ConverterManager.getJdbcTypeName(columnType)+" fieldType:"+fieldType.getCanonicalName(), this);	
				}
			}
		}
		return converterObject;
	}

	public Collection<Node> getChildren() {
		return null;
	}
	
	private Integer columnType = null;
	public int getColumnType(CaffeinConfig caffeinConfig) throws Exception {
		if(columnType == null){
			String table = null;
			if (getParent() instanceof ManyToMany) {//ManyToMany
				table = ((ManyToMany) getParent()).getTable();
			} else if (getParent() instanceof ManyToOne) {//ManyToOne
				Node clazz = getParent();
				while(clazz.getParent() != null) {//!(clazz instanceof Clazz)
					clazz = clazz.getParent();
				}
				table = ((Clazz) clazz).getTable();
			} else {//OneToMany
				table = ((Relation) getParent()).getForeignClazz(caffeinConfig).getTable();
			}
			try {
				columnType = caffeinConfig.getColumnType(table, getColumn());
			} catch (Exception e) {
				throw new ClazzConfigException("column or table not found", e, this);
			}
		}
		return columnType;
	}
	
	private Boolean columnNullable = null;
	public boolean isColumnNullable(CaffeinConfig caffeinConfig) throws Exception {
		if(columnNullable == null){
			String table = null;
			if (getParent() instanceof ManyToMany) {//ManyToMany
				table = ((ManyToMany) getParent()).getTable();
			} else if (getParent() instanceof ManyToOne) {//ManyToOne
				Node clazz = getParent();
				while(clazz.getParent() != null) {//!(clazz instanceof Clazz)
					clazz = clazz.getParent();
				}
				table = ((Clazz) clazz).getTable();
			} else {//OneToMany
				table = ((Relation) getParent()).getForeignClazz(caffeinConfig).getTable();
			}
			try {
				columnNullable = caffeinConfig.isColumnNullable(table, getColumn());
			} catch (Exception e) {
				throw new ClazzConfigException("column or table not found", e, this);
			}
		}
		return columnNullable;
	}

	public String toString() {
		return getClass().getName()+"["+column+"]";
	}
}